package com.citywithincity.db;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.citywithincity.db.annotation.AutoGenerate;
import com.citywithincity.db.annotation.Column;
import com.citywithincity.db.annotation.ColumnConvert;
import com.citywithincity.db.annotation.ColumnIgnore;
import com.citywithincity.db.annotation.Table;
import com.citywithincity.db.annotation.Unique;
import com.citywithincity.db.data2db.Data2DbCreator;
import com.citywithincity.db.data2entity.Data2EntityCreator;
import com.citywithincity.db.interfaces.IPsSetter;
import com.citywithincity.db.interfaces.IValueConvert;
import com.citywithincity.db.interfaces.SqlDriver;
import com.citywithincity.db.meta.ColumnStruct;
import com.citywithincity.db.meta.DbStructFactory;
import com.citywithincity.db.meta.TableStruct;
import com.citywithincity.utils.CachedClassUtil;
import com.citywithincity.utils.ClassUtil;
import com.citywithincity.utils.StringUtils;

public class EntityBuilder {
	
	static{
		TableBindConfig.load();
	}
	
	
	public static void bindEntity(String className) throws Exception {
		bindEntity(Class.forName(className));
	}
	
	
	public static void bindEntity(Class<?> clazz){
		//查找这个entity对应的db\tableName\pk设置
		if(map.containsKey(clazz)){
			return;
		}
		//或者查找设置
		TableBindInfo config = TableBindConfig.geTableBindInfo(clazz);
		if(config!=null){
			//这里进行绑定
			bindEntity(clazz,config);
			return;
		}
		
		Table table = clazz.getAnnotation(Table.class);
		if(table!=null){
			bindEntity(table,clazz);
		}
	}

	private static void bindEntity(Table table,Class<?> clazz) {
		bindEntity(table.db(), table.name(),table.pk(), table.prefix(), clazz);
	}

	
	public static void bindEntity(Class<?> clazz,TableBindInfo config) {
		bindEntity(config.getDb(), config.getTable(),config.getPk(), config.isPrefix(), clazz);
	}
	
	
	@SuppressWarnings("rawtypes")
	private static Map<Class<?>, Entity> map = new ConcurrentHashMap<Class<?>, Entity>();
	static PsSetter defaultPsSetter = new PsSetter();
	@SuppressWarnings({ "unchecked", "rawtypes" })
	private static void bindEntity(int db,String table,String pk,boolean prefix,Class<?> clazz){
		DbStructFactory struct = Db.getStruct(db);
		SqlDriver driver = Db.getDriver(db);
		TableStruct tableStruct = struct.getTableStruct(table);
	
		//对每一个字段解析
		List<Field> fields = getFields(clazz);
		
		List<EntityField> list = new ArrayList<EntityField>();
		List<EntityField> uniqueFields = new ArrayList<EntityField>(2);
		Map<String, ColumnStruct> shortMap = getShortMap(tableStruct, prefix);
		Field autoGenerate = null;
		
		IValueConvert autoGenerateValueConvert = null;
		
		String autoGenerateColumn = null;
		int index = 0;
		for (Field field : fields) {
			
			Column column = field.getAnnotation(Column.class);
			ColumnStruct columnStruct = null;
			TableStruct currentTableStruct = null;
			if(column!=null){
				if(column.value().contains(" AS")){
					//
					currentTableStruct = tableStruct;
					columnStruct =  new ColumnStruct(table);// tableStruct.getColumn(columnName.substring(columnName.indexOf("AS")+3));
					String columnName = column.value();
					if(columnName.contains("SUM") 
							|| columnName.contains("MAX") 
							|| columnName.contains("MIN") 
							|| columnName.contains("EVERAGE")){
						
						ColumnStruct orgColumn = tableStruct.getColumn(columnName.substring(columnName.indexOf("AS")+3));
						if(orgColumn==null){
							throw new RuntimeException("找不到表"+table+" 字段:"+columnName.substring(columnName.indexOf("AS")+3)  );
						}
						
						if(orgColumn.getClazz()==Integer.class){
							columnStruct.setType(Types.DECIMAL);
							columnStruct.setClazz(BigDecimal.class);
						}else if(orgColumn.getClazz() == BigDecimal.class){
							columnStruct.setType(Types.DECIMAL);
							columnStruct.setClazz(BigDecimal.class);
						}else if(orgColumn.getClazz() == Double.class){
							columnStruct.setType(Types.DOUBLE);
							columnStruct.setClazz(Double.class);
						}
						
						columnStruct.setName(columnName.substring(columnName.indexOf("AS")+3));
						columnStruct.setSelect(column.value());
						
					}else{
						columnStruct.setClazz(String.class);
						columnStruct.setName(columnName.substring(columnName.indexOf("AS")+3));
						columnStruct.setType(Types.VARCHAR);
						columnStruct.setSelect(column.value());
					}
				}else{
					if(column.value().contains(".")){
						//其他表的，本entity只支持select,不支持其他操作
						String columnName = column.value();
						String tableName = columnName.substring(0,columnName.indexOf('.'));
						currentTableStruct= struct.getTableStruct(tableName); 
						if(currentTableStruct==null){
							throw new RuntimeException("找不到表"+tableName);
						}
						columnStruct = currentTableStruct.getColumn(columnName.substring(columnName.indexOf('.')+1));
					}else{
						currentTableStruct = tableStruct;
						columnStruct = tableStruct.getColumn(column.value());
					}
				}
				
			}else{
				currentTableStruct = tableStruct;
				columnStruct = shortMap.get(field.getName());
			}
			if(columnStruct==null){
				throw new RuntimeException("找不到字段"+field.getName() + " Class:"+field.getDeclaringClass());
			}
			
			EntityField entityField = new EntityField(currentTableStruct,columnStruct,field,index++);
			//自动编号
			if(field.isAnnotationPresent(AutoGenerate.class)){
				autoGenerateValueConvert = Data2EntityCreator.createEntityConver(field, driver.getAutoIncreaseClass(),driver);
				autoGenerateColumn = columnStruct.getName();
				autoGenerate = field;
			}
			
			if(field.isAnnotationPresent(Unique.class)){
				uniqueFields.add(entityField);
			}
			
			//db to entity  
			entityField.setToEntityConvert(
				Data2EntityCreator.createEntityConver(
						field, 
						columnStruct.getClazz(),
						driver
				)
			);
			//entity to db
			//entityField.setPsSetter(createPsSetter(field, columnStruct.getClazz()));
			entityField.setColumnStruct(columnStruct);
			entityField.psSetter = createPsSetter(field, columnStruct.getClazz());
			list.add(entityField);
		}
		
		EntityField[] entityFields = new EntityField[list.size()];
		list.toArray(entityFields);
		
		
		Entity entityInfo = new Entity(clazz,db,driver.protectTableName(table),
				getPks(pk, entityFields, tableStruct), getPk(pk, entityFields, tableStruct) );
		map.put(clazz, entityInfo);
		
		entityInfo.setFields(entityFields);
		entityInfo.setTableName(tableStruct.getName());
		entityInfo.setUniqueFields(uniqueFields);
		entityInfo.autoGenerageField = autoGenerate;
		entityInfo.autoGenerateConvert = autoGenerateValueConvert;
		entityInfo.autoGenerageColumn = autoGenerateColumn;
		entityInfo.select = buildSelect(entityFields,driver);
	}
	
	private static final Log logger = LogFactory.getLog(EntityBuilder.class);
	
	private static String getPk(String pk, EntityField[] entityFields, TableStruct tableStruct) {
		if(!StringUtils.isEmpty(pk)){return pk;}
		if(tableStruct.getPks().length>0){
			return tableStruct.getPks()[0].getName();
		}
		logger.warn("表"+tableStruct.getName()+"没有定义主键");
		return null;
	}


	private static EntityField find(EntityField[] entityFields,String columnName){
		for (EntityField entityField : entityFields) {
			if(entityField.columnName.equals(columnName)){
				return entityField;
			}
		}
		return null;
		//throw new RuntimeException("找不到"+columnName+ " Class:" + entityFields[0].field.getDeclaringClass());
	}
	
	private static EntityField[] getPks(String pk,EntityField[] entityFields,TableStruct struct){
		List<EntityField> list = new ArrayList<EntityField>(2);
		//pk
		String[] pks = null;
		if(!StringUtils.isEmpty(pk)){
			pks = pk.split(",");
			for (String string : pks) {
				list.add(find(entityFields, string)  );
			}
		}else{
			//程序的pk
			ColumnStruct[] tablePks = struct.getPks();
			for (ColumnStruct c : tablePks) {
				list.add(find(entityFields, c.getName())  );
			}
		}
		EntityField[] fields = new EntityField[list.size()];
		list.toArray(fields);
		return fields;
	}
	

	
	
	private static String buildSelect(EntityField[] entityFields,SqlDriver driver) {
		StringBuilder sb = new StringBuilder();
		boolean first = true;
		for (EntityField entityField : entityFields) {
			if(first){
				first = false;
			}else{
				sb.append(',');
			}
			if(entityField.getColumnStruct().getSelect()!=null){
				sb.append(entityField.getColumnStruct().getSelect());
			}else{
				sb.append(driver.protectTableName(entityField.getTableStruct().getName())).append('.').append(entityField.columnName);
			}
			
		}
		return sb.toString();
	}

	
	
	
	static class ValueConvertPsSetter implements IPsSetter{

		private IValueConvert convert;
		private IPsSetter setter;
		
		public ValueConvertPsSetter(IValueConvert convert,IPsSetter setter) {
			this.convert = convert;
			this.setter = setter;
		}
		
		@Override
		public void setValue(PreparedStatement ps, int index, Object value) throws SQLException {
			value = convert.convert(value);
			setter.setValue(ps, index, value);
		}
	}
	
	
	
	
	private static IPsSetter createPsSetter(Field field, Class<?> columnClass){
		
		ColumnConvert convert = field.getAnnotation(ColumnConvert.class);
		if(convert!=null && convert.toDb()!=IValueConvert.class){
			//target
			IValueConvert valueConvert = CachedClassUtil.newInstance(convert.toDb());
			IPsSetter setter = createPsSetterPure(convert.target(),field, columnClass);
			return new ValueConvertPsSetter(valueConvert,setter  );
		}else{
			IPsSetter setter = createPsSetterPure(field.getType(), field,columnClass);
			return setter;
		}
		
	}
	
	private static IPsSetter createPsSetterPure(Class<?> fieldClass,Field field, Class<?> columnClass) {
		try {
			
			if(fieldClass.getName().equals( columnClass.getName()) || ClassUtil.isWapClass(fieldClass,columnClass)){
				return defaultPsSetter;
			}else{
				return Data2DbCreator.create(fieldClass,columnClass);
			}
		}catch (Exception e) {	
			throw new RuntimeException("Class "+field.getDeclaringClass()+" field "+field.getName()+" data convert:"+e.getMessage(),e);
		}
		
	}
	

	public static Map<String, ColumnStruct> getShortMap(TableStruct tableStruct, boolean prefix){
		Collection<ColumnStruct> list = tableStruct.getColumns();
		String columnPrefix = "";
		if(prefix){
			columnPrefix = parsePrefix(list);
		}
		Map<String, ColumnStruct> shortMap = new HashMap<String, ColumnStruct>(list.size());
		for (ColumnStruct struct : list) {
			String shortName = getShortName(columnPrefix, struct.getName());
			shortMap.put(shortName, struct);
		}
		return shortMap;
	}
	
	/**
	 * 获取所有可以绑定的字段
	 * @param clazz
	 * @return
	 */
	private static List<Field> getFields(Class<?> clazz){
		List<Field> result = new ArrayList<Field>();
		Field[] fields = ClassUtil.getFields(clazz);
		for (Field field : fields) {
			if(field.isAnnotationPresent(ColumnIgnore.class)){
				continue;
			}
			field.setAccessible(true);
			result.add(field);
		}
		return result;
	}


	@SuppressWarnings("unchecked")
	public static <T> Entity<T> getEntity(Class<T> clazz) {
		return map.get(clazz);
	}

	private static Pattern BIG_PATTERN = Pattern.compile("[A-Z]+");
	
	private static String parsePrefix(Collection<ColumnStruct> list){
		Map<String, Integer> countMap = new HashMap<String, Integer>();
		for (ColumnStruct columnInfo : list) {
			String[] arr = columnInfo.getName().split("_");
			if(countMap.containsKey(arr[0])){
				countMap.put(arr[0], countMap.get(arr[0])+1);
			}else{
				countMap.put(arr[0], 1);
			}
		}
		
		String maxKey = "";
		int max = 0;
		for (Entry<String, Integer> entry : countMap.entrySet()) {
			if(max < entry.getValue()){
				max = entry.getValue();
				maxKey = entry.getKey();
			}
		}
		
		if(BIG_PATTERN.matcher(maxKey).matches()){
			return maxKey.toLowerCase()+ "_";
		}
		return maxKey+ "_";
		
	}
	private static Pattern PATTERN = Pattern.compile("[A-Z\\d_]+");
	private static String getShortName(String columnPrefix,String name){
		
		if(PATTERN.matcher(name).matches()){
			name = name.toLowerCase();
		}
		
		if(name.startsWith(columnPrefix)){
			name = name.substring(columnPrefix.length());
		}
		
		String[] names = name.split("_");
		StringBuilder result = new StringBuilder();
		int index = 0;
		for (String string : names) {
			if(index>0){
				char[] arr = string.toCharArray();
				arr[0] = Character.toUpperCase(arr[0]);
				result.append(arr);
			}else{
				result.append(string);
			}
			++index;
		}
		
		return result.toString();
	}
}
